package drds.plus.sql_process.abstract_syntax_tree.node.query;

import drds.plus.common.jdbc.Parameters;
import drds.plus.sql_process.abstract_syntax_tree.ObjectCreateFactory;
import drds.plus.sql_process.abstract_syntax_tree.configuration.ColumnMetaData;
import drds.plus.sql_process.abstract_syntax_tree.configuration.IndexMapping;
import drds.plus.sql_process.abstract_syntax_tree.configuration.TableMetaData;
import drds.plus.sql_process.abstract_syntax_tree.execute_plan.ExecutePlan;
import drds.plus.sql_process.abstract_syntax_tree.execute_plan.query.JoinStrategy;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.Item;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.column.Column;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.BooleanFilter;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.Filter;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.Function;
import drds.plus.sql_process.abstract_syntax_tree.expression.item.function.Operation;
import drds.plus.sql_process.abstract_syntax_tree.expression.order_by.OrderBy;
import drds.plus.sql_process.abstract_syntax_tree.node.dml.Delete;
import drds.plus.sql_process.abstract_syntax_tree.node.dml.Insert;
import drds.plus.sql_process.abstract_syntax_tree.node.dml.Replace;
import drds.plus.sql_process.abstract_syntax_tree.node.dml.Update;
import drds.plus.sql_process.abstract_syntax_tree.node.query.build.QueryBuilder;
import drds.plus.sql_process.abstract_syntax_tree.node.query.build.TableQueryBuilder;
import drds.plus.sql_process.optimizer.chooser.share_delegate.ShareDelegateAnnotation;
import drds.plus.sql_process.utils.DnfFilters;
import drds.plus.sql_process.utils.OptimizerUtils;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * 查询某个具体的真实表的Node 允许使用这个node，根据查询条件进行树的构建
 */
public class TableQuery extends Query {
    @Setter
    @Getter
    private final TableQueryBuilder tableQueryNodeBuilder;
    @Setter
    @Getter
    private String tableName;
    @Setter
    @Getter
    private List<String> actualTableNameList = new ArrayList<String>(); // 比如存在水平分表时，tableName代表逻辑表名,actualTableName代表物理表名
    @Setter
    @Getter
    private TableMetaData tableMetaData;
    @Setter
    @Getter
    private boolean fullTableScan = false; // 是否需要全表扫描
    @Setter
    @Getter
    private IndexMapping indexMappingUsed = null; // 当前逻辑表的使用index
    @Setter
    @Getter
    private Filter indexQueryValueFilter = null;

    public TableQuery() {
        this(null);
    }

    public TableQuery(String tableName) {
        super();
        this.tableName = tableName;
        tableQueryNodeBuilder = new TableQueryBuilder(this);
    }

    public void build() {
        if (this.isNeedBuild()) {
            this.tableQueryNodeBuilder.build();
        }

        setNeedBuild(false);
    }

    public void assignment(Parameters parameters) {
        super.assignment(parameters);
        this.indexQueryValueFilter = OptimizerUtils.assignment(indexQueryValueFilter, parameters);
    }

    public ExecutePlan toExecutePlan(int shareIndex) {
        // 不能传递shareIndex,代理对象会自处理
        return this.convertToJoinIfNeed().toExecutePlan();
    }

    /**
     * 根据索引信息构建查询树，可能需要进行主键join
     *
     * <pre>
     * 分支：
     * 1. 没选择索引，直接按照主键进行全表扫描
     * 2. 选择了索引
     *      a. 选择的是主键索引，直接按照主键构造查询
     *      b. 选择的是非主键索引，需要考虑做主键二次join查询
     *          i. 如果索引信息里包含了所有的选择字段，直接基于主键查询返回，一次查询就够了
     *          ii. 包含了非索引中的字段，需要做回表查询.
     *              先根据索引信息查询到主键，再根据主键查询出所需的其他字段，对应的join条件即为主键字段
     * </pre>
     */

    public Query convertToJoinIfNeed() {
        String $tableName = this.getTableName();
        if (this.getIndexMappingUsed() == null || this.getIndexMappingUsed().isPrimaryKeyIndex()) {
            // 若不包含索引，则扫描主表即可或者使用主键索引
            TableQueryWithIndex tableQueryWithIndex = new TableQueryWithIndex(this.getTableMetaData().getPrimaryKeyIndexMetaData().getIndexName());
            // 如果有别名，用别名，否则，用逻辑表名替代索引名
            tableQueryWithIndex.setAliasAndSetNeedBuild(this.getName());
            tableQueryWithIndex.setParent(this.getParent());
            //
            tableQueryWithIndex.setSelectItemListAndSetNeedBuild(OptimizerUtils.copySelectItemList(this.getSelectItemList(), $tableName, tableQueryWithIndex.getAlias()));
            tableQueryWithIndex.indexQueryKeyFilter(OptimizerUtils.copyFilter(this.getIndexQueryKeyFilter(), $tableName, tableQueryWithIndex.getAlias()));
            tableQueryWithIndex.resultFilter(DnfFilters.and(OptimizerUtils.copyFilter(this.getIndexQueryValueFilter(), $tableName, tableQueryWithIndex.getAlias()), OptimizerUtils.copyFilter(this.getResultFilter(), $tableName, tableQueryWithIndex.getAlias())));
            tableQueryWithIndex.setGroupByListAndSetNeedBuild(OptimizerUtils.copyOrderBys(this.getGroupByList(), $tableName, tableQueryWithIndex.getAlias()));
            tableQueryWithIndex.having(OptimizerUtils.copyFilter(this.getHaving(), $tableName, tableQueryWithIndex.getAlias()));
            tableQueryWithIndex.setOrderByListAndSetNeedBuild(OptimizerUtils.copyOrderBys(this.getOrderByList(), $tableName, tableQueryWithIndex.getAlias()));
            tableQueryWithIndex.setLimitFrom(this.getLimitFrom());
            tableQueryWithIndex.setLimitTo(this.getLimitTo());
            //
            tableQueryWithIndex.setOtherJoinOnFilter(OptimizerUtils.copyFilter(this.getOtherJoinOnFilter(), $tableName, tableQueryWithIndex.getAlias()));
            //
            tableQueryWithIndex.setSubQueryAndSetNeedBuild(this.isSubQuery());
            tableQueryWithIndex.setSubQueryFunctionFilter(OptimizerUtils.copyFilter(this.getSubQueryFunctionFilter(), $tableName, tableQueryWithIndex.getAlias()));
            tableQueryWithIndex.setCorrelatedSubQuery(this.isCorrelatedSubQuery());
            //
            tableQueryWithIndex.setFullTableScan(this.isFullTableScan());
            tableQueryWithIndex.setLockMode(this.getLockMode());
            for (int i = 0; i < this.getShareSize(); i++) {
                tableQueryWithIndex.setDataNodeId(i, this.getDataNodeId(i));
            }
            tableQueryWithIndex.setSql(this.getSql());
            tableQueryWithIndex.build();
            return tableQueryWithIndex;
        } else { // 非主键索引
            IndexMapping indexMapping = this.getIndexMappingUsed();
            TableQueryWithIndex tableQueryWithIndex = new TableQueryWithIndex(indexMapping.getIndexName());
            tableQueryWithIndex.setParent(this.getParent());
            // 索引是否都包含在查询字段中
            boolean isIndexCover = true;
            List<Item> selectItemList = new ArrayList<Item>();
            List<Item> referedItemList = this.getReferedItemList();
            for (Item item : referedItemList) {
                if (item instanceof Function) {
                    continue;
                }
                boolean isSameName = item.getTableName().equals(this.getAlias()) || item.getTableName().equals(this.getTableName());
                if (this.isSubQuery() && this.getSubAlias() != null) {
                    isSameName |= item.getTableName().equals(this.getSubAlias());
                }
                if (!isSameName) {
                    // 针对correlated subquery,可能存在非当前表的列，忽略之
                    continue;
                }
                ColumnMetaData columnMetaData = indexMapping.getColumnMetaData(item.getColumnName());
                if (columnMetaData == null) {
                    isIndexCover = false;
                } else {
                    Column column = ObjectCreateFactory.createColumn();
                    column.setColumnName(item.getColumnName());
                    selectItemList.add(column);//这里只有索引
                }
            }
            // 索引覆盖的情况下，只需要返回索引查询
            if (isIndexCover) {
                tableQueryWithIndex.setAliasAndSetNeedBuild(this.getName());
                //
                tableQueryWithIndex.setSelectItemListAndSetNeedBuild(OptimizerUtils.copySelectItemList(this.getSelectItemList(), $tableName, tableQueryWithIndex.getAlias()));
                tableQueryWithIndex.indexQueryKeyFilter(OptimizerUtils.copyFilter(this.getIndexQueryKeyFilter(), $tableName, tableQueryWithIndex.getAlias()));
                //basedOnIndexTableQueryNode.resultFilter(OptimizerUtils.copyFilter(this.getIndexQueryValueFilter(), $tableName, basedOnIndexTableQueryNode.getAlias()));
                tableQueryWithIndex.resultFilter(DnfFilters.and(OptimizerUtils.copyFilter(this.getIndexQueryValueFilter(), $tableName, tableQueryWithIndex.getAlias()), OptimizerUtils.copyFilter(this.getResultFilter(), $tableName, tableQueryWithIndex.getAlias())));
                tableQueryWithIndex.setGroupByListAndSetNeedBuild(OptimizerUtils.copyOrderBys(this.getGroupByList(), $tableName, tableQueryWithIndex.getAlias()));
                tableQueryWithIndex.having(OptimizerUtils.copyFilter(this.getHaving(), $tableName, tableQueryWithIndex.getAlias()));
                tableQueryWithIndex.setOrderByListAndSetNeedBuild(OptimizerUtils.copyOrderBys(this.getOrderByList(), $tableName, tableQueryWithIndex.getAlias()));
                tableQueryWithIndex.setLimitFrom(this.getLimitFrom());
                tableQueryWithIndex.setLimitTo(this.getLimitTo());
                //
                tableQueryWithIndex.setOtherJoinOnFilter(OptimizerUtils.copyFilter(this.getOtherJoinOnFilter(), $tableName, tableQueryWithIndex.getAlias()));
                //
                tableQueryWithIndex.setSubQueryAndSetNeedBuild(this.isSubQuery());
                tableQueryWithIndex.setSubQueryFunctionFilter(OptimizerUtils.copyFilter(this.getSubQueryFunctionFilter(), $tableName, tableQueryWithIndex.getAlias()));
                tableQueryWithIndex.setCorrelatedSubQuery(this.isCorrelatedSubQuery());
                //
                tableQueryWithIndex.setWhereAndSetNeedBuild(this.getWhere());
                //
                for (int i = 0; i < this.getShareSize(); i++) {
                    tableQueryWithIndex.setDataNodeId(i, this.getDataNodeId(i));
                }
                tableQueryWithIndex.setLockMode(this.getLockMode());
                tableQueryWithIndex.build();
                return tableQueryWithIndex;
            } else {
                tableQueryWithIndex.setAliasAndSetNeedBuild(indexMapping.getNameWithOutDot());
                //
                tableQueryWithIndex.setSelectItemListAndSetNeedBuild(selectItemList);
                tableQueryWithIndex.indexQueryKeyFilter(OptimizerUtils.copyFilter(this.getIndexQueryKeyFilter(), $tableName, tableQueryWithIndex.getAlias()));
                tableQueryWithIndex.resultFilter(OptimizerUtils.copyFilter(this.getIndexQueryValueFilter(), $tableName, tableQueryWithIndex.getAlias()));
                // 不是索引覆盖的情况下，需要回表，就是索引查询和主键查询
                IndexMapping primaryKeyIndexMapping = this.getTableMetaData().getPrimaryKeyIndexMetaData();
                // 由于按照主键join，主键也是被引用的列
                for (ColumnMetaData columnMetaData : primaryKeyIndexMapping.getKeyColumnMetaDataList()) {
                    boolean flag = false;
                    for (Item referedItem : referedItemList) {
                        boolean isSameName = referedItem.getTableName().equals(this.getAlias()) || referedItem.getTableName().equals(this.getTableName());
                        if (this.isSubQuery() && this.getSubAlias() != null) {
                            isSameName |= referedItem.getTableName().equals(this.getSubAlias());
                        }
                        if (!isSameName) {
                            // 针对correlated subquery,可能存在非当前表的列，忽略之
                            flag = true;//表名不相同
                            continue;
                        }
                        if (columnMetaData.getColumnName().equals(referedItem.getColumnName())) {
                            flag = true;//表名相同 列名相同
                            break;
                        }
                    }
                    if (!flag) {//添加主键
                        Column column1 = ObjectCreateFactory.createColumn();
                        column1.setColumnName(columnMetaData.getColumnName());

                        Column column2 = ObjectCreateFactory.createColumn();
                        column2.setColumnName(columnMetaData.getColumnName());

                        referedItemList.add(column1);
                        tableQueryWithIndex.addSelectItem(column2);//仅仅索引列
                    }
                }

                List<Item> selectItemListOfIndexQueryNodeWithPrimaryKey = new ArrayList<Item>();
                TableQueryWithIndex tableQueryWithIndexWithPrimaryKey = new TableQueryWithIndex(primaryKeyIndexMapping.getIndexName());
                tableQueryWithIndexWithPrimaryKey.setAliasAndSetNeedBuild(this.getName());
                tableQueryWithIndexWithPrimaryKey.setParent(this.getParent());
                //
                for (Item item : referedItemList) {
                    // 函数应该回表的时候做
                    if (item instanceof Function) {
                        continue;
                    }
                    boolean isSameName = item.getTableName().equals(this.getAlias()) || item.getTableName().equals(this.getTableName());
                    if (this.isSubQuery() && this.getSubAlias() != null) {
                        isSameName |= item.getTableName().equals(this.getSubAlias());
                    }
                    if (!isSameName) {
                        // 针对correlated subquery,可能存在非当前表的列，忽略之
                        continue;
                    }
                    //当前表的所有referedItemList字段
                    Column column1 = ObjectCreateFactory.createColumn();
                    column1.setColumnName(item.getColumnName());
                    selectItemListOfIndexQueryNodeWithPrimaryKey.add(column1);
                }
                tableQueryWithIndexWithPrimaryKey.setSelectItemListAndSetNeedBuild(selectItemListOfIndexQueryNodeWithPrimaryKey);//所有的有效字段
                //如果valueFilter中有index中的列，实际应该在indexQuery中做
                tableQueryWithIndexWithPrimaryKey.resultFilter(OptimizerUtils.copyFilter(this.getResultFilter(), $tableName, tableQueryWithIndexWithPrimaryKey.getAlias()));

                Join joinNode = tableQueryWithIndex.join(tableQueryWithIndexWithPrimaryKey);
                // 根据主键列构建join关联条件
                for (ColumnMetaData columnMetaData : primaryKeyIndexMapping.getKeyColumnMetaDataList()) {
                    BooleanFilter booleanFilter = ObjectCreateFactory.createBooleanFilter();
                    booleanFilter.setOperation(Operation.equal);
                    Column column1 = ObjectCreateFactory.createColumn();
                    column1.setColumnName(columnMetaData.getColumnName());
                    column1.setTableName(indexMapping.getIndexName());

                    Column column2 = ObjectCreateFactory.createColumn();
                    column2.setColumnName(columnMetaData.getColumnName());
                    column2.setTableName(primaryKeyIndexMapping.getIndexName());

                    booleanFilter.setColumn(column1);
                    booleanFilter.setValue(column2);
                    joinNode.addJoinFilter(booleanFilter);
                }
                String tableName = this.getTableName();
                if (this.getAlias() != null) {
                    tableName = this.getAlias();
                }
                //
                joinNode.setAlias(this.getAlias());
                joinNode.setParent(this.getParent());
                joinNode.setTableSubAliasAndSetNeedBuild(this.getSubAlias());
                joinNode.setPrimaryKeyJoin(true);
                //
                joinNode.setSelectItemListAndSetNeedBuild(OptimizerUtils.copySelectItemList(this.getSelectItemList(), $tableName, tableName));
                joinNode.setGroupByListAndSetNeedBuild(OptimizerUtils.copyOrderBys(this.getGroupByList(), $tableName, tableName));
                joinNode.having(OptimizerUtils.copyFilter(this.getHaving(), $tableName, tableName));
                joinNode.setOrderByListAndSetNeedBuild(OptimizerUtils.copyOrderBys(this.getOrderByList(), $tableName, tableName));
                joinNode.setLimitFrom(this.getLimitFrom());
                joinNode.setLimitTo(this.getLimitTo());
                //
                joinNode.setOtherJoinOnFilter(OptimizerUtils.copyFilter(this.getOtherJoinOnFilter(), $tableName, tableName));
                // 回表是IndexNestedLoop
                joinNode.setJoinStrategy(JoinStrategy.index_nest_loop_join);
                //
                joinNode.setSubQueryAndSetNeedBuild(this.isSubQuery());
                joinNode.setSubQueryFunctionFilter(OptimizerUtils.copyFilter(this.getSubQueryFunctionFilter(), $tableName, tableName));
                joinNode.setCorrelatedSubQuery(this.isCorrelatedSubQuery());
                //
                joinNode.setWhereAndSetNeedBuild(this.getWhere());
                //
                joinNode.setLockMode(this.getLockMode());
                for (int i = 0; i < this.getShareSize(); i++) {
                    joinNode.setDataNodeId(i, this.getDataNodeId(i));
                }

                joinNode.build();
                return joinNode;
            }
        }
    }

    public List getCompleteOrderByList() {
        // 如果有显示group by，直接使用group by
        List<OrderBy> orderByListCombinedWithGroupByList = getOrderByListCombinedWithGroupByList();
        if (orderByListCombinedWithGroupByList != null) {
            return orderByListCombinedWithGroupByList;
        } else {
            return new ArrayList();
        }
    }

    public QueryBuilder getQueryNodeBuilder() {
        return tableQueryNodeBuilder;
    }

    public String getName() {
        if (this.getAlias() != null) {
            return this.getAlias();
        }
        return this.getTableName();
    }

    public Insert insert(List<Item> columns, List values) {
        Insert insertNode = new Insert(this);
        insertNode.setColumnNameList(columns);
        if (values != null && values.size() > 0 && values.get(0) instanceof List) {
            // 处理insert多value
            insertNode.setIsValueListList(true);
            insertNode.setValueListList(values);
        } else {
            insertNode.setColumnValueList(values);
        }
        return insertNode;
    }


    public Insert insert(String columns, Object[] values) {
        if (columns == null) {
            return this.insert(new String[]{}, values);
        }
        return this.insert(columns.split(" "), values);
    }

    public Insert insert(String[] columns, Object[] values) {
        List<Item> cs = new LinkedList<Item>();
        for (String name : columns) {
            Item s = OptimizerUtils.createColumnFromString(name);
            cs.add(s);
        }

        List<Object> valueList = new ArrayList<Object>(Arrays.asList(values));
        return this.insert(cs, valueList);
    }

    public Insert insert(String columns, List values) {
        if (columns == null) {
            return this.insert(new String[]{}, values);
        }
        return this.insert(columns.split(" "), values);
    }

    public Insert insert(String[] columns, List values) {
        List<Item> itemList = new LinkedList<Item>();
        for (String name : columns) {
            Item item = OptimizerUtils.createColumnFromString(name);
            itemList.add(item);
        }

        return this.insert(itemList, values);
    }

    public Replace put(List<Item> columns, List values) {
        Replace replaceNode = new Replace(this);
        replaceNode.setColumnNameList(columns);
        if (values.size() > 0 && values.get(0) instanceof List) {
            // 处理insert多value
            replaceNode.setIsValueListList(true);
            replaceNode.setValueListList(values);
        } else {
            replaceNode.setColumnValueList(values);
        }
        return replaceNode;
    }

    public Replace put(String columns, Object[] values) {
        if (columns == null) {
            return this.put(new String[]{}, values);
        }
        return put(columns.split(""), values);
    }

    public Replace put(String[] columns, Object[] values) {
        List<Item> itemList = new LinkedList<Item>();
        for (String name : columns) {
            Item item = OptimizerUtils.createColumnFromString(name);
            itemList.add(item);
        }

        List<Object> valueList = new ArrayList<Object>(Arrays.asList(values));
        return put(itemList, valueList);
    }

    public Replace put(String columns, List values) {

        if (columns == null) {
            return this.put(new String[]{}, values);
        }

        return this.put(columns.split(" "), values);
    }

    public Replace put(String[] columns, List values) {
        List<Item> itemList = new LinkedList<Item>();
        for (String name : columns) {
            Item item = OptimizerUtils.createColumnFromString(name);
            itemList.add(item);
        }

        return this.put(itemList, values);
    }

    public Update update(List<Item> columnNameList, List<Object> columnValueList) {
        if (columnNameList.size() != columnValueList.size()) {
            throw new IllegalArgumentException("The size of the columnNameList and columnValueList is not matched." + " columnNameList' size is " + columnNameList.size() + ". columnValueList' size is " + columnValueList.size());
        }

        Update updateNode = new Update(this);
        updateNode.setUpdateColumnNameList(columnNameList);
        updateNode.setUpdateColumnValueList(columnValueList);

        return updateNode;
    }

    public Update update(String columns, Object[] values) {
        return update(columns.split(" "), values);
    }

    public Update update(String[] columnNames, Object[] columnValues) {
        List<Item> itemList = new LinkedList<Item>();
        for (String name : columnNames) {
            Item item = OptimizerUtils.createColumnFromString(name);
            itemList.add(item);
        }

        List<Object> columnValueList = new ArrayList<Object>(Arrays.asList(columnValues));
        return update(itemList, columnValueList);
    }

    public Delete delete() {
        Delete deleteNode = new Delete(this);
        return deleteNode;
    }


    public TableQuery copy() {

        TableQuery newTableQueryNode = new TableQuery(null);
        this.copySelfTo(newTableQueryNode);
        return newTableQueryNode;
    }

    public TableQuery copySelf() {
        return copy();
    }

    protected void copySelfTo(Query query) {
        super.copySelfTo(query);
        TableQuery toTableQueryNode = (TableQuery) query;
        toTableQueryNode.setFullTableScan(this.isFullTableScan());
        toTableQueryNode.setIndexQueryValueFilter((Filter) (indexQueryValueFilter == null ? null : indexQueryValueFilter.copy()));
        toTableQueryNode.tableName = this.tableName;
        toTableQueryNode.actualTableNameList = new ArrayList<String>(this.actualTableNameList);
        toTableQueryNode.setTableMetaData(this.getTableMetaData());
        toTableQueryNode.setIndexMappingUsed(this.getIndexMappingUsed());
    }

    public TableQuery deepCopy() {
        TableQuery newTableQueryNode = new TableQuery(null);
        this.deepCopySelfTo(newTableQueryNode);
        return newTableQueryNode;
    }

    protected void deepCopySelfTo(Query query) {
        super.deepCopySelfTo(query);
        TableQuery toTableQueryNode = (TableQuery) query;
        toTableQueryNode.setFullTableScan(this.isFullTableScan());
        toTableQueryNode.setIndexQueryValueFilter((Filter) (indexQueryValueFilter == null ? null : indexQueryValueFilter.copy()));
        toTableQueryNode.tableName = this.tableName;
        toTableQueryNode.actualTableNameList = new ArrayList<String>(this.actualTableNameList);
        toTableQueryNode.setTableMetaData(this.getTableMetaData());
        toTableQueryNode.setIndexMappingUsed(this.getIndexMappingUsed());
    }


    @ShareDelegateAnnotation
    public String getActualTableName() {
        return getActualTableName(0);
    }

    @ShareDelegateAnnotation
    public void setActualTableName(String actualTableName) {
        setActualTableName(0, actualTableName);
    }

    public String getActualTableName(int shareIndex) {
        ensureCapacity(actualTableNameList, shareIndex);
        return actualTableNameList.get(shareIndex);
    }

    public void setActualTableName(int shareIndex, String actualTableName) {
        ensureCapacity(actualTableNameList, shareIndex);
        this.actualTableNameList.set(shareIndex, actualTableName);

    }


}
